1. Technical Field
The present invention relates to an improved data processing system. In particular, the present invention relates to Java™ native function calls in a data processing system. Still more particular, the present invention relates to transforming Java™ Native Interface function calls into constants, internal compiler operations or simpler operations in a data processing system.
2. Description of Related Art
In this disclosure, Java™ (“Java”) and all Java-based marks, such a Java virtual machine, JVM™ (“JVM”), Java Native Interface, JNI™ (“JNI”), and Java Development Kit, JDK™ (“JDK”), are trademarks or registered trademarks of Sun Microsystems, Inc., or its subsidiaries in the United States and other countries.
Java is an object oriented programming language and environment focusing on defining data as objects and the methods that may be applied to those objects. Java supports only single inheritance, meaning that each class can inherit from only one other class at any given time. Java also allows for the creation of totally abstract classes known as interfaces, which allow the defining of methods that may be shared with several classes without regard for how other classes are handling the methods. Java provides a mechanism to distribute software and extends the capabilities of a Web browser because programmers can write an applet once and the applet can be run on any Java enabled machine on the Web.
The Java virtual machine (JVM) is a virtual computer component. The JVM allows Java programs to be executed on different platforms as opposed to only the one platform for which the code was compiled. Java programs are compiled for the JVM. In this manner Java is able to support applications for many types of data processing systems, which may contain a variety of central processing units and operating systems architectures. To enable a Java application to execute on different types of data processing systems, a compiler typically generates an architecture-neutral file format—the compiled code is executable on many processors, given the presence of the Java run time system. The Java compiler generates Bytecode instructions that are non-specific to particular computer architectures. A Bytecode is a machine independent code generated by the Java compiler and executed by a Java interpreter. A Java interpreter is a module in the JVM that alternatively decodes and executes a Bytecode or Bytecodes. These Bytecode instructions are designed to be easy to interpret on any machine and easily translated on the fly into native machine code.
A development environment, such as the Java Development Kit (JDK) available from Sun Microsystems, Inc., may be used to build Java Bytecode from Java language source code and libraries. This Java Bytecode may be stored as a Java application or applet on a Web Server, where it can be downloaded over a network to a user's machine and executed on a local JVM.
The Java run-time environment is specifically designed to limit the harm that a Java application can cause to the system that it is running on. This is especially important with the World Wide Web, where Java applets are downloaded and executed automatically when a user visits a Web page that contains Java applets. Normally one would not want to execute random programs; they might contain viruses, or they might even be potentially malicious themselves and not merely carrying unwelcome code unintentionally. Unless the user specifically allows it (by setting the appropriate flags in the user-interface to the JVM), a Java applet cannot read or write to attached storage devices (except perhaps to a specific, restricted area), nor can it read or write to memory locations (except to a specific, restricted area).
Not only are Java applets designed for downloading over the network, standard Java libraries also specifically support client-server computing. The Java language includes provisions for multi-threading and for network communications. Compared to other languages (such as C), it is much easier to write a pair of programs, one executing locally on the user's computer that is handling the user-interaction, and the other executing remotely on a server, which is performing potentially more sophisticated and processor-intensive work.
While the Java language is designed to be platform-independent and to execute primarily in a secure environment, programmers can extend Java applications through the use of compiled native binary code on the host operating system using C-style calling conventions through the Java Native Interface (JNI). In this fashion, a Java application can have complete access to the host operating system, including reading and writing to attached I/O devices, memory, etc. Because of this, Java programs can accomplish tasks that are not normally allowed via the JVM at the cost of being platform-specific. However, with a well-designed architecture, a Java language programmer can cleanly isolate the platform-independent portion, and present a clean, platform-independent object API to other Java components while at the same time accomplishing platform-specific tasks.
With the JNI API, parameters and class data residing inside the Java Virtual Machine (JVM) as well as services provided by the JVM are accessible and may be modified. However, since JNI exposes a very platform independent and opaque representation of the JVM, its data and services, overheads exist. The JNI API is typically exposed by a pointer to a table of function pointers (referred to as the JNI environment pointer, or JNIEnv) to JNI function implementations. Although the pointer is essential to JNI's platform independence, the pointer may also be costly because additional branches and table lookups are performed during a JNI function call. The cost of a JNI invocation depends on the type of JNI function being called.
For example, to access string and array parameters passed from Java to native code, special invocations of JNI functions are required. These special invocations cause expensive runtime copy operations to avoid touching the JVM's copy of the data. The JNI API thus provides accessor functions that attempt to increase the chances of native code receiving direct references to underlying JVM data. However, these functions are implemented at the JVM's discretion and the use of these functions places certain restrictions on the programmer's freedom.
For field and method access, JNI function calls are also required to modify objects and access JVM services from the native code. For example, to modify an object's field or call a class's method, a handle to the appropriate data must first be retrieved. This retrieval is typically implemented as a traversal on the JVM's reflective data structures, in addition to expensive string-based signature comparison operations at runtime. This traversal is orders of magnitude slower than direct field accesses in Java. In addition, JNI function calls may stall if a non-asynchronous JVM is performing blocking work.
Other JNI functions have a unique set of overheads similar to JNI functions for field and method access. These JNI functions include functions that instantiate objects, manage references, handle exceptions, support synchronization and reflection, and ones that are in the Java invocation API for embedding JVMs inside native code.
Several attempts have been made to minimize the overhead of JNI function calls. The first of these attempts are programmer-based optimizations in the form of efficient coding techniques when writing native code that uses the JNI API. The JNI specification also provides a set of critical functions that may return direct references to JVM data and objects and suggests ways to avoid calling into JNI API, for example, caching field and method IDs during static initialization of classes.
Another attempt involves removing native function related overheads by minimizing dependencies on JNI API functions by restricting the type of functionality that can be offered in native code. However, this mechanism may only be used for methods that are guaranteed not to require garbage collection, exception handling, synchronization or any type of security support since these are the types of functionality the wrappers provide.
In yet another attempt to minimize the overhead of JNI function calls, compilers may use proprietary native interfaces that are tightly coupled to the VM, since the interfaces have knowledge of the internals of the VM. An example of such compilers is the Jcc compiler, which is an optimizing compiler that compiles directly to native code using a proprietary native interface reminiscent of the original Native Method Interface (NMI), from Sun Microsystems, Inc. However, the NMI has been replaced by the JNI. In Jcc, a Java method that is marked ‘native’ will be treated as a C method to be called with C calling conventions and emitting a C structure for every Java class. In this way, Jcc may inline short assembly segment to speed up native calls.
While the above attempts minimizes some of the overhead associated with JNI function calls, there is no existing mechanism that does not restrict the type of functionality implemented in native code, as mentioned in the second attempt, or does not couple to a specific VM implementation, as mentioned in the third attempt.
Therefore, it would be advantageous to have a method, an apparatus, and computer instructions for use by a Just in Time (JIT) compiler transforming JNI function calls to constants, internal compiler operations or a simpler intermediate representation, such that performance of native codes accessing JVM data and services may be improved without coupling to a specific VM implementation or sacrificing type safety of the JNI.